#include "dns.h"

/**
 * Resolves a domainname to an ip address
 * It makes use of net_gethostbyname from libogc, which in turn makes use of a Wii BIOS function
 * Just like the net_gethostbyname function this function is NOT threadsafe!
 *
 * @param char* The domain name to resolve
 * @return u32 The ipaddress represented by four bytes inside an u32 (in network order)
 */
u32 getipbyname(char *domain)
{
        //Care should be taken when using net_gethostbyname,
        //it returns a static buffer which makes it not threadsafe
        //TODO: implement some locking mechanism to make below code atomic
        struct hostent *host = net_gethostbyname(domain);

        if(host == NULL) {
                return 0;
        }

        u32 *ip = (u32*)host->h_addr_list[0];
        return *ip;
}



//Defines how many DNS entries should be cached by getipbynamecached()
#define MAX_DNS_CACHE_ENTRIES 20

//The cache is defined as a linked list,
//The last resolved domain name will always be at the front
//This will allow heavily used domainnames to always stay cached
struct dnsentry {
        char *domain;
        u32 ip;
        struct dnsentry *nextnode;
} ;

static struct dnsentry *firstdnsentry = NULL;
static int dnsentrycount = 0;

/**
 * Performs the same function as getipbyname(),
 * except that it will prevent extremely expensive net_gethostbyname() calls by caching the result
 */
u32 getipbynamecached(char *domain)
{
        //Search if this domainname is already cached
        struct dnsentry *node = firstdnsentry;
        struct dnsentry *previousnode = NULL;

        while(node != NULL)
        {
                if(strcmp(node->domain, domain) == 0)
                {
                        //DNS node found in the cache, move it to the front of the list
                        if(previousnode != NULL)
                                previousnode->nextnode = node->nextnode;

                        if(node != firstdnsentry)
                                node->nextnode = firstdnsentry;
                        firstdnsentry = node;

                        return node->ip;
                }
                //Go to the next element in the list
                previousnode = node;
                node = node->nextnode;
        }
        u32 ip = getipbyname(domain);

        //No cache of this domain could be found, create a cache node and add it to the front of the cache
        struct dnsentry *newnode = malloc(sizeof(struct dnsentry));
        if(newnode == NULL) {
                return ip;
        }

        newnode->ip = ip;
        newnode->domain = malloc(strlen(domain)+1);
        if(newnode->domain == NULL)
        {
                free(newnode);
                return ip;
        }
        strcpy(newnode->domain, domain);

        newnode->nextnode = firstdnsentry;
        firstdnsentry = newnode;
        dnsentrycount++;

        //If the cache grows too big delete the last (and probably least important) node of the list
        if(dnsentrycount > MAX_DNS_CACHE_ENTRIES)
        {
                struct dnsentry *node = firstdnsentry;
                struct dnsentry *previousnode = NULL;

                //Fetch the last two elements of the list
                while(node->nextnode != NULL)
                {
                        previousnode = node;
                        node = node->nextnode;
                }

                if(node == NULL)
                {
                        printf("Configuration error, MAX_DNS_ENTRIES reached while the list is empty\n");
                        exit(1);
                } else if(previousnode == NULL)
                {
                        firstdnsentry = NULL;
                } else {
                        previousnode->nextnode = NULL;
                }

                free(node->domain);
                free(node);
                dnsentrycount--;
        }

        return newnode->ip;
}